package com.apobates.forum.member.impl.service;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.TreeMap;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import com.apobates.forum.event.elderly.ForumActionEnum;
import com.apobates.forum.event.elderly.MemberAction;
import com.apobates.forum.event.elderly.MemberActionDescriptor;
import com.apobates.forum.member.MemberBaseProfile;
import com.apobates.forum.member.MemberProfile;
import com.apobates.forum.member.MemberProfileBean;
import com.apobates.forum.member.api.dao.ForumScoreRoleDao;
import com.apobates.forum.member.api.dao.MemberActiveRecordsDao;
import com.apobates.forum.member.api.dao.MemberDao;
import com.apobates.forum.member.api.dao.MemberNamesProtectDao;
import com.apobates.forum.member.api.dao.RegisteInviteCodeDao;
import com.apobates.forum.member.api.service.MemberLevelService;
import com.apobates.forum.member.api.service.MemberService;
import com.apobates.forum.member.entity.ForumScoreRole;
import com.apobates.forum.member.entity.Member;
import com.apobates.forum.member.entity.MemberGroupEnum;
import com.apobates.forum.member.entity.MemberNamesProtect;
import com.apobates.forum.member.entity.MemberRoleEnum;
import com.apobates.forum.member.entity.MemberStatusEnum;
import com.apobates.forum.member.entity.RegisteInviteCode;
import com.apobates.forum.member.exception.MemberNamesExistException;
import com.apobates.forum.member.exception.MemberNamesProtectException;
import com.apobates.forum.member.impl.event.MemberEventPublisher;
import com.apobates.forum.member.impl.event.MemberSignInEvent;
import com.apobates.forum.member.impl.event.MemberSignOutEvent;
import com.apobates.forum.member.impl.event.MemberSignUpEvent;
import com.apobates.forum.utils.Commons;
import com.apobates.forum.utils.DateTimeUtils;
import com.apobates.forum.utils.lang.TriFunction;

@Service
@CacheConfig(cacheNames = "memberCache")
public class MemberServiceImpl implements MemberService{
	@Autowired
	private MemberDao memberDao;
	@Autowired
	private ForumScoreRoleDao forumScoreRoleDao;
	@Autowired
	private MemberEventPublisher memberEventPublisher;
	@Autowired
	private MemberNamesProtectDao memberNamesProtectDao;
	@Autowired
	private RegisteInviteCodeDao registeInviteCodeDao;
	@Autowired
	private MemberActiveRecordsDao memberActiveRecordsDao;
	@Autowired
	private MemberLevelService memberLevelService;
	private final static Logger logger = LoggerFactory.getLogger(MemberServiceImpl.class);
	
	@Cacheable(key="'avatar_'+#id")
	@Override
	public Optional<String> getMemberAvatar(long id) { //[MC]M2
		logger.info("[MemberSRV]get member avatar arg:"+id);
		return memberDao.getAvatar(id);
	}
	
	@MemberAction(action = ForumActionEnum.MEMBER_PROFILE_AVATAR)
	@CacheEvict(key="'avatar_'+#id")
	@Override
	public Optional<Boolean> updateAvatar(long id, String encodeAvtarFormatPath, MemberActionDescriptor actionDescriptor) { //[MC]M2-R1
		return memberDao.editAvatar(id, encodeAvtarFormatPath);
	}
	
	@Override
	public Optional<Member> get(long id) {
		if(id>0){
			//清理属性,保证对外安全
			return securityMember(memberDao.findOneById(id));
		}
		return Optional.empty();
	}
	
	@MemberAction(action = ForumActionEnum.MEMBER_LOGOUT, argName="memberNames", argType=String.class)
	@Override
	public Optional<Boolean> signOut(String memberNames, Member member, MemberActionDescriptor actionDescriptor) {
		memberEventPublisher.publishSignOutEvent(new MemberSignOutEvent(this, member, actionDescriptor.getReferrer(), actionDescriptor.getIpAddr()));
		return Optional.of(true);
	}

	@MemberAction(action = ForumActionEnum.MEMBER_REGISTER, argName="memberNames", argType=String.class)
	@Override
	public Optional<Member> signUp(String memberNames, String unEncryptPswd, String inviteCode, String nickname, MemberActionDescriptor actionDescriptor)throws IllegalStateException{
		//--------------------------------------------------------------邀请码检查
		long inviteCodeId = verifyInviteCode(inviteCode);
		//--------------------------------------------------------------登录帐号的唯一性|名称保护检查
		try{
			checkNamesUnique(memberNames);
		}catch(MemberNamesProtectException | MemberNamesExistException e){
			throw new IllegalStateException(e.getMessage());
		}
		//--------------------------------------------------------------
		String salt = Commons.randomAlphaNumeric(6);
		String encryptPswd = Commons.sha256(unEncryptPswd, salt);
		Member m = new Member(0, memberNames, encryptPswd, salt, nickname);
		m.setInviteCode(inviteCode);
		m.setInviteCodeId(inviteCodeId);
		try {
			memberDao.save(m);
			if(m.getId()>0) {
				//清理属性,保证对外安全
				Optional<Member> tmp = securityMember(m);
				if(tmp.isPresent()){
					Member eventMember = tmp.get();
					// 丢掉了inviteCode相关信息
					eventMember.setInviteCodeId(inviteCodeId);
					eventMember.setInviteCode(inviteCode);
					memberEventPublisher.publishSignUpEvent(new MemberSignUpEvent(this, eventMember));
				}
				//
				return tmp;
			}
		}catch(Exception e) {
			throw new IllegalStateException("注册会员操作失败");
		}
		throw new IllegalStateException("会员创建失败");
	}
	
	@MemberAction(action = ForumActionEnum.MEMBER_REGISTER, argName="memberNames", argType=String.class)
	@Override
	public long create(String memberNames, String unEncryptPswd, String nickname, MemberRoleEnum role, MemberActionDescriptor actionDescriptor)throws IllegalStateException{
		//--------------------------------------------------------------检查会员登录帐号的唯一性
		try{
			checkNamesUnique(memberNames);
		}catch(MemberNamesProtectException | MemberNamesExistException e){
			throw new IllegalStateException(e.getMessage());
		}
		//--------------------------------------------------------------
		String salt = Commons.randomAlphaNumeric(6);
		String encryptPswd = Commons.sha256(unEncryptPswd, salt);
		Member m = new Member(0, memberNames, encryptPswd, salt, MemberStatusEnum.ACTIVE, MemberGroupEnum.LEADER, role, nickname);
		try {
			memberDao.save(m);
			if(m.getId()>0) {
				//
				return m.getId();
			}
		}catch(Exception e) {
			throw new IllegalStateException("创建社区经理操作失败", e);
		}
		throw new IllegalStateException("会员创建失败");
	}

	@MemberAction(action = ForumActionEnum.MEMBER_LOGIN, argName="memberNames", argType=String.class)
	@Override
	public Optional<Member> signIn(String memberNames, String unEncryptPswd, MemberActionDescriptor actionDescriptor)throws IllegalStateException {
		Optional<String> encryptPswd = getMemberSalt(memberNames).map(psalt -> Commons.sha256(unEncryptPswd, psalt));
		if(encryptPswd.isPresent()) {
			Optional<Member> tmp = securityMember(memberDao.findOne(memberNames, encryptPswd.get())); //清理属性,保证对外安全
			if(tmp.isPresent()){
				Member member = tmp.get();
				memberEventPublisher.publishSignInEvent(new MemberSignInEvent(this, member, actionDescriptor.getIpAddr(), actionDescriptor.getReferrer(), actionDescriptor.getDevice()));
				//
				return tmp;
			}
			throw new IllegalStateException("用户名与密码不匹配");
		}
		throw new IllegalStateException("用户名不存在");
	}

	@Override
	public Optional<String> getMemberSalt(String memberNames) {
		return memberDao.findSalt(memberNames);
	}
	

	@MemberAction(action = ForumActionEnum.MEMBER_LOGIN, argName="memberNames", argType=String.class)
	@Override
	public Optional<Member> signIn(String memberNames, String unEncryptPswd, boolean isAdmin, MemberActionDescriptor actionDescriptor)throws IllegalStateException {
		if(!isAdmin) {
			return signIn(memberNames, unEncryptPswd, actionDescriptor);
		}
		Optional<String> encryptPswd = getMemberSalt(memberNames).map(psalt -> Commons.sha256(unEncryptPswd, psalt));
		if(encryptPswd.isPresent()) {
			Optional<Member> data = memberDao.findOneForAdmin(memberNames, encryptPswd.get());
			//清理属性,保证对外安全
			return securityMember(data);
		}
		throw new IllegalStateException("会员不存在或暂时无法访问");
	}

	@MemberAction(action = ForumActionEnum.MEMBER_PASSPORT)
	@Override
	public Optional<Boolean> editPswd(long id, String oldUnEncryptPswd, String newUnEncryptPswd, MemberActionDescriptor actionDescriptor)throws IllegalStateException {
		Optional<String> encryptPswd=memberDao.findSalt(id).map(psalt -> Commons.sha256(oldUnEncryptPswd, psalt));
		if(encryptPswd.isPresent()) {
			String newSalt = Commons.randomAlphaNumeric(6);
			String newEncryptPswd = Commons.sha256(newUnEncryptPswd, newSalt);
			return memberDao.editPswd(id, encryptPswd.get(), newEncryptPswd, newSalt)==1?Optional.of(true):Optional.empty();
		}
		throw new IllegalStateException("会员密码更新失败");
	}

	@MemberAction(action = ForumActionEnum.MEMBER_PASSPORT_REST)
	@Override
	public Optional<Boolean> resetPswd(long id, String newUnEncryptPswd, MemberActionDescriptor actionDescriptor) {
		String newSalt = Commons.randomAlphaNumeric(6);
		String newEncryptPswd = Commons.sha256(newUnEncryptPswd, newSalt);
		return memberDao.resetPswd(id, newEncryptPswd, newSalt)==1?Optional.of(true):Optional.empty();
	}

	@Override
	public long existMemberNames(String memberNames) {
		return memberDao.exists(memberNames);
	}
	//@notsafe
	@Override
	public Stream<Member> searchByNames(String suggestNameWord, int pageSize) {
		return memberDao.searchByNames(suggestNameWord, pageSize);
	}
	//@notsafe
	@Override
	public Stream<Member> searchByNames(String suggestNameWord) {
		return memberDao.searchByNames(suggestNameWord);
	}
	
	@MemberAction(action = ForumActionEnum.MEMBER_PROFILE_BASE)
	@Override
	public Optional<Boolean> edit(long id, String signature, String nickname, MemberActionDescriptor actionDescriptor) {
		return memberDao.editNicknameAndSignature(id, nickname, signature);
	}

	//@notsafe
	@Override
	public Stream<Member> queryCollection(List<Long> memberIdList) {
		if(memberIdList==null || memberIdList.isEmpty()){
			return Stream.empty();
		}
		return memberDao.findAllByIdList(memberIdList);
	}
	//@notsafe
	@Override
	public Stream<Member> getRecent(int size) {
		return memberDao.findAllOfRecent(size);
	}

	@Override
	public long count() {
		return memberDao.count();
	}
	
	@Override
	public Optional<MemberProfileBean> getMemberProfileBean(final long id, final EnumMap<ForumActionEnum, Long> actionStats) {
		return calcMemberProfileBeanAsync(id, actionStats);
	}

	@Override
	public List<MemberProfileBean> getMemberProfileBeanes(final List<Long> idList, final Map<Long, EnumMap<ForumActionEnum, Long>> memberActionStats) {
		BiFunction<Long, Map<ForumActionEnum, Long>, Optional<MemberProfileBean>> fun = (memberId, actionStats)->calcMemberProfileBeanAsync(memberId, actionStats);
		return idList.stream().map(mid->fun.apply(mid, memberActionStats.get(mid))).filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList());
	}
	
	// 是禁止返回true
	private boolean checkNamesProtect(String memberNames) {
		Stream<MemberNamesProtect> nativeMnp = MemberNamesProtect.getNativeProtect().stream().map(MemberNamesProtect::new);
		Stream<MemberNamesProtect> definedMnp = memberNamesProtectDao.findAll(true);
		return Stream.concat(nativeMnp, definedMnp).filter(x -> memberNames.toLowerCase().equals(x.getMemberNames().toLowerCase())).findAny().isPresent();
	}
	
	@Override
	public Optional<Boolean> checkNamesUnique(String namesword)throws MemberNamesProtectException, MemberNamesExistException{
		boolean namesProtectResult = checkNamesProtect(namesword);
		if(namesProtectResult){
			 throw new MemberNamesProtectException("帐号暂时不允许使用");
		}
		
		long mid = memberDao.exists(namesword);
		if(mid>0){
			throw new MemberNamesExistException("用户名已经存在");
		}
		return Optional.of(true);
	}

	@Override
	public TreeMap<String, Long> groupMemberForBirthDate(LocalDateTime start, LocalDateTime finish) {
		return memberDao.groupMemberForBirthDate(start, finish);
	}

	@Override
	public EnumMap<MemberStatusEnum, Long> groupMemberForStatus() {
		return memberDao.groupMemberForStatus();
	}

	@Override
	public EnumMap<MemberRoleEnum, Long> groupMemberForRole() {
		return memberDao.groupMemberForRole();
	}

	@Override
	public EnumMap<MemberGroupEnum, Long> groupMemberForGroup() {
		return memberDao.groupMemberForGroup();
	}

	@Override
	public Map<String, String> getActiveInviteCode(String inviteCode) {
		long inviteCodeId = 0;
		try {
			inviteCodeId = verifyInviteCode(inviteCode);
		} catch (IllegalArgumentException e) {
		}
		Map<String, String> data = new HashMap<>();
		data.put("code", inviteCode);
		data.put("id", String.valueOf(inviteCodeId));
		return data;
	}
	
	private Optional<MemberProfileBean> calcMemberProfileBeanAsync(final long id, final Map<ForumActionEnum, Long> otherActionStats) {
		final Optional<Member> member = get(id);
		if (!member.isPresent()) {
			return Optional.of(MemberProfileBean.guest());
		}
		final TriFunction<Map<ForumActionEnum, Long>, Stream<ForumScoreRole>, Optional<Member>, Optional<MemberProfile>> action = (stats, scoreRoles, om) -> {
			Member m = om.get();
			return MemberProfile
					.init(m.getId(), m.getNickname(), m.getMgroup(), m.getMrole(), MemberBaseProfile.getStyle(m.getMgroup(), m.getMrole()), m.getSignature())
					.calcMemberScore(m.getId(), stats, scoreRoles.collect(Collectors.toList()));
		};
		CompletableFuture<Optional<MemberProfile>> mid = getMemberCalcActionAsync(id, otherActionStats).thenCombine(getScoleRolesAsync(), (mapStruct, forumScoreRole) -> action.apply(mapStruct, forumScoreRole, member));
		return mid.thenCombine(CompletableFuture.supplyAsync(() -> memberLevelService.getMemberLevelCommonBean()),
				(omp, mlbeans) -> {
					omp.ifPresent(mp -> mp.calcMemberLevel(mlbeans));
					return omp;
				}).join().map(MemberProfile::toResultBean).orElseGet(() -> Optional.of(MemberProfileBean.guest()));
	}
	
	private CompletableFuture<Map<ForumActionEnum, Long>> getMemberCalcActionAsync(final long id, final Map<ForumActionEnum, Long> otherActionStats) {
		CompletableFuture<Map<Boolean, List<ForumScoreRole>>> scoreRoleDefineSet = getScoleRolesAsync().thenApply(fsrs -> fsrs.collect(Collectors.partitioningBy(fsr -> fsr.getAction().getSection().equals("member"))));
		return scoreRoleDefineSet
				.thenCompose(
						mp -> CompletableFuture.supplyAsync(() -> mp.getOrDefault(false, new ArrayList<>()).stream().collect(Collectors.toMap(ForumScoreRole::getAction, fsr -> Commons.optional(otherActionStats.getOrDefault(fsr.getAction(), 0L), 0L))))
								.thenCombine(
										CompletableFuture.supplyAsync(() -> memberActiveRecordsDao.statsMemberAction(id, mp.getOrDefault(true, new ArrayList<>()).stream().map(ForumScoreRole::getAction).collect(Collectors.toList()))),
										(otherActiones, memberActiones) -> {
											HashMap<ForumActionEnum, Long> data = new HashMap<>();
											data.putAll(otherActiones);
											data.putAll(memberActiones);
											return data;
										}));
	}

	private CompletableFuture<Stream<ForumScoreRole>> getScoleRolesAsync() {
		return CompletableFuture.supplyAsync(() -> forumScoreRoleDao.findAllUsed().filter(Objects::nonNull));
	}
	private Optional<Member> securityMember(Member member){
		if(null == member){
			return Optional.empty();
		}
		
		Member tmp = new Member();
		tmp.setAvatarURI(member.getAvatarURI());
		tmp.setId(member.getId());
		tmp.setMgroup(member.getMgroup());
		tmp.setMrole(member.getMrole());
		tmp.setNickname(member.getNickname());
		tmp.setSignature(member.getSignature());
		tmp.setTdparty(member.getTdparty());
		tmp.setRegisteDateTime(member.getRegisteDateTime());
		tmp.setStatus(member.getStatus());
		tmp.setNames(member.getNames());
		return Optional.of(tmp);
	}
	private Optional<Member> securityMember(Optional<Member> member){
		Member m = member.orElse(null);
		return securityMember(m);
	}

	private long verifyInviteCode(String inviteCode) throws IllegalArgumentException {
		long inviteCodeId = 0L;
		if (Commons.isNotBlank(inviteCode)) {
			RegisteInviteCode ic = registeInviteCodeDao.findOne(inviteCode).orElseThrow(() -> new IllegalArgumentException("邀请码不存在或已经失效"));
			// 邀请码只能是当天有效的
			if (!DateTimeUtils.isSameDay(ic.getBuildDateTime(), LocalDateTime.now())) {
				throw new IllegalArgumentException("当前邀请码已经过期");
			}
			inviteCodeId = ic.getId();
		}
		return inviteCodeId;
	}
}
